package filenavigation

import (
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/goccy/go-json"
	"github.com/gogf/gf/errors/gerror"
	"github.com/h2non/filetype"
	"gitlab.local/DO-module/new-filemanage-module/src/constant"
	"gitlab.local/DO-module/new-filemanage-module/src/controller/errcode"
	utils2 "gitlab.local/DO-module/new-filemanage-module/src/service/access/utils"
	"gitlab.local/DO-module/new-filemanage-module/src/service/file_safeBox"
	"gitlab.local/TerraMaster/tos-modules/database/sqlitemodel"
	fileoperate2 "gitlab.local/TerraMaster/tos-modules/fileoperate"
	"gitlab.local/golibrary/linux"
	"gitlab.local/golibrary/picture/heic"
	"gitlab.local/golibrary/user"
	"gitlab.local/golibrary/utils"
	"io"
	"io/fs"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"time"
)

var VolumeRegexP = regexp.MustCompile(`^/Volume\d+($|/)`)

type multipartHttpFile struct {
	h *multipart.FileHeader
}

func (Self *multipartHttpFile) Filename() string {
	return Self.h.Filename
}

func (Self *multipartHttpFile) Open() (io.ReadCloser, error) {
	return Self.h.Open()
}

// NavLocation 多媒体播放(用于分享)
func (n *FileNav) NavLocation(userName string) ([]Location, error) {
	var locations []Location
	var folders []sqlitemodel.Share
	_ = n.dbGetter.DbGet().Find(&folders)
	u, err := user.Lookup(userName)
	if err != nil {
		return locations, err
	}
	locationMap := make(map[string]bool)
	for _, item := range folders {
		if item.Type == "ISO" || item.Ecryptfs == 1 {
			if dfItem := linux.GetDeviceInfoByPath(item.MntPath); dfItem == nil {
				continue
			}
		}
		if _, ok := locationMap[item.Device]; ok {
			continue
		}
		var acl string
		if strings.Index(item.MntPath, "@usb") > -1 || strings.Index(item.MntPath, "@drives") > -1 {
			acl = n.unix(item.MntPath, u)
		} else {
			acl, err = n.aclService.UserAcl(item.MntPath, userName)
			if err != nil {
				continue
			}
		}
		//无权限
		if !strings.Contains(acl, "r") {
			continue
		}
		location := Location{}
		location.Path = item.MntPath
		location.FType = item.Type
		location.Name = item.FolderName
		if strings.Contains(item.Device, "Volume") {
			location.Name = item.Device
			locationMap[item.Device] = true
		}
		location.Volume = item.Device
		location.VolumePath = item.MntPath
		location.Type = Volume
		locations = append(locations, location)

	}

	return locations, nil
}

func (n *FileNav) unix(path string, userInfo *user.User) (unix string) {
	unix = "---"
	// 切换用户
	if err := utils.SwitchUserRight(userInfo.Name); err != nil {
		return
	}
	fileInfo, err := os.Stat(path)
	if err != nil {
		return
	}
	perm := fileInfo.Mode().Perm()
	// 根据是否为超级用户
	if userInfo.Gid == "0" {
		unix = perm.String()[1:4]
	} else {
		unix = perm.String()[7:10]
	}
	// fmt.Printf("Group: %s\n", perm.String()[4:7])
	return
}

// GetHomePath 获取home目录路径
func (n *FileNav) GetHomePath(userName string) (map[string]string, error) {
	data := make(map[string]string)
	u, err := user.Lookup(userName)
	if err != nil {
		return data, err
	}

	data["name"] = userName
	data["path"] = u.HomeDir
	return data, nil
}

// ShareMultimediaPlay 多媒体播放(用于分享)
func (n *FileNav) ShareMultimediaPlay(shareLink, pwd, path string, c *gin.Context) error {
	if err := n.VerifyShareMultimediaPlay(shareLink, pwd, path); err != nil {
		return err
	}
	return n.MultimediaPlay("", path, c)
}

// MultimediaPlay 多媒体播放
func (n *FileNav) MultimediaPlay(uname, path string, c *gin.Context) error {
	stat, err := os.Stat(path)
	if err != nil {
		return gerror.Wrap(err, "MultimediaPlay")
	}
	if stat.IsDir() {
		return errors.New("the path is not a file")
	}
	if uname != "" {
		//如果用户名称存在则判断权限，不存在则不判断
		if !n.aclService.IsRead(path, uname) {
			return gerror.NewCode(errcode.PermissionDenied)
		}
	}

	var size int64 = 32774 //一般文件只需前262个字符，iso类大文件可能需要更大
	if stat.Size() < size {
		size = stat.Size()
	}
	file, err := os.Open(path)
	if err != nil {
		return gerror.Wrap(err, "MultimediaPlay1")
	}
	buf := make([]byte, size)
	if _, err = file.Read(buf); err != nil {
		_ = file.Close()
		return gerror.Wrap(err, "MultimediaPlay2")
	}
	if !n.CheckFileClass(buf, path) {
		_ = file.Close()
		return errors.New("file type not supported")
	}

	var baseName string

	//heif图像特殊处理
	if filetype.IsExtension(buf, "heif") {
		webp, err := n.heicToTmpWebp(file)
		_ = file.Close() //关闭原文件句柄
		if err != nil {
			return gerror.Wrap(err, "MultimediaPlay2")
		}
		file, err = os.Open(webp) //覆盖原文件句柄
		if err != nil {
			return gerror.Wrap(err, "MultimediaPlay3")
		}
		baseName = strings.TrimSuffix(filepath.Base(path), filepath.Ext(path)) + filepath.Ext(webp)
	} else {
		baseName = filepath.Base(path)
	}

	info, err := file.Stat()
	if err != nil {
		return gerror.Wrap(err, "MultimediaPlay4")
	}

	c.Header("Content-Disposition", fmt.Sprintf(`inline;filename="%s";filename*=utf-8;`, baseName))
	http.ServeContent(c.Writer, c.Request, baseName, info.ModTime(), file)
	_ = file.Close()

	return nil
}

func (n *FileNav) IsVideo(buf []byte, path string) bool {
	if filetype.IsVideo(buf) {
		return true
	}
	out, err := utils.ShellExec(fmt.Sprintf(`file --mime-type '%s'`, path))
	if err != nil {
		return false
	}
	return strings.Contains(out, " video/")
}

// CheckFileClass 获取文件归限制
func (n *FileNav) CheckFileClass(buf []byte, path string) bool {
	//图像,音频,视频,pdf
	return filetype.IsImage(buf) || filetype.IsAudio(buf) || filetype.IsExtension(buf, "pdf") || n.IsVideo(buf, path)
}

// heicToTmpWebp heic转换为webp
func (n *FileNav) heicToTmpWebp(filenameIn *os.File) (string, error) {
	md5 := utils.GetIoMd5(filenameIn)
	if md5 == "" {
		return "", errors.New("get md5 error")
	}
	dir := filepath.Join(linux.GetMainVolume(), "@thumbnail", md5)
	//缩略图路径
	distSrc := filepath.Join(dir, fmt.Sprintf("%s.webp", md5))
	if utils.Exists(distSrc) {
		return distSrc, nil
	}
	if err := os.MkdirAll(dir, 0755); err != nil {
		return "", err
	}
	create, err := os.Create(distSrc)
	if err != nil {
		return "", err
	}
	defer create.Close()
	if err = heic.ToWebp(filenameIn, create); err != nil {
		return "", err
	}
	return distSrc, nil
}

// GetRecentlyFileFilter 获取最近文件
func (n *FileNav) GetRecentlyFileFilter(userName string) ([]fileoperate2.FoldersInfo, error) {
	data := make([]fileoperate2.FoldersInfo, 0)
	recently, err := n.ReadRecently(userName)
	if err != nil {
		return data, gerror.Wrap(err, "GetRecentlyFileFilter")
	}

	var (
		wg    sync.WaitGroup
		mutex sync.Mutex
	)

	for _, v := range recently {
		wg.Add(1)
		go func(path string) {
			defer wg.Done()
			folder, err := n.fOptGetter.GetFolder(path)
			if err != nil {
				return
			}
			folder.VolumePath, _ = utils.SwitchShowName(path)
			mutex.Lock()
			data = append(data, *folder)
			mutex.Unlock()
		}(v)
	}

	wg.Wait()

	sort.SliceStable(data, func(i, j int) bool {
		return data[i].MTime > data[j].MTime
	})

	return data, nil
}

// GetSafeBoxFileSearch 保险箱搜索
func (n *FileNav) GetSafeBoxFileSearch(fileName, username string, page, limit int) ([]fileoperate2.FoldersInfo, error) {
	data := make([]fileoperate2.FoldersInfo, 0)

	safePath := n.GetSafeBoxPath(username)
	if safePath == "" {
		return data, nil
	}

	type info struct {
		path string
		name string
	}

	srcArr := make([]info, 0)
	fileName = strings.ToLower(fileName) //忽略大小写

	err := filepath.WalkDir(safePath, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if !strings.Contains(strings.ToLower(d.Name()), fileName) {
			return nil
		}
		srcArr = append(srcArr, info{
			name: d.Name(),
			path: path,
		})
		return nil
	})

	//排序
	sort.SliceStable(srcArr, func(i, j int) bool {
		return srcArr[i].name > srcArr[j].name
	})

	skip := 0
	if page > 0 {
		skip = (page - 1) * limit
	}

	if len(srcArr) > skip+limit {
		srcArr = srcArr[skip : skip+limit]
	} else if len(srcArr) > skip {
		srcArr = srcArr[skip:]
	} else {
		srcArr = nil
	}

	for _, v := range srcArr {
		folder, err := n.fOptGetter.GetFolder(v.path)
		if err != nil {
			continue
		}
		data = append(data, *folder)
	}

	return data, err
}

func ExistSafeBox(uname, mountSafeBoxDir string) error {
	lookup, err := user.Lookup(uname)
	if err != nil {
		return err
	}
	uid, _ := strconv.Atoi(lookup.Uid)
	gid, _ := strconv.Atoi(lookup.Gid)

	if !utils.Exists(mountSafeBoxDir) {
		if err = os.Mkdir(mountSafeBoxDir, 0700); err != nil {
			return gerror.Wrap(err, "Create dir fail")
		}
		_ = os.Chown(mountSafeBoxDir, uid, gid)
	}
	return nil
}

// GetSafeBoxList 获取保险箱列表
func (n *FileNav) GetSafeBoxList(userName string, page, limit int) ([]fileoperate2.FoldersInfo, error) {
	list := make([]fileoperate2.FoldersInfo, 0)
	lookup, err := user.Lookup(userName)
	if err != nil {
		return list, err
	}

	mountSafeBoxDir := fmt.Sprintf(file_safeBox.MountSafeBoxDir, lookup.HomeDir)
	if ExistSafeBox(userName, mountSafeBoxDir) != nil {
		return list, err
	}
	//判断是否开启保险箱
	if !n.IsMount(mountSafeBoxDir) {
		return list, err
	}

	entries, err := ioutil.ReadDir(mountSafeBoxDir)
	if err != nil {
		return list, err
	}

	if limit > 0 {
		skip := 0
		if page > 0 {
			skip = (page - 1) * limit
		}
		if len(entries) > skip+limit {
			entries = entries[skip : skip+limit]
		} else if len(entries) > skip {
			entries = entries[skip:]
		} else {
			entries = nil
		}
	}

	for _, entry := range entries {
		path := filepath.Join(mountSafeBoxDir, entry.Name())
		folder, err := n.fOptGetter.GetFolder(path)
		if err != nil {
			continue
		}
		list = append(list, *folder)
	}

	return list, nil
}

func (n *FileNav) GetDocumentList(userName string) (*fileoperate2.FoldersInfo, error) {
	var list *fileoperate2.FoldersInfo
	u, err := user.Lookup(userName)
	if err != nil {
		return list, err
	}

	list, err = n.fOptGetter.GetFolder(u.HomeDir)
	if err != nil {
		return list, gerror.Wrap(err, "GetHomeListFilter")
	}

	return list, nil
}

// GetShareFolder 获取分享上传的目录
func (n *FileNav) GetShareFolder(shareLink string) (string, error) {

	link, err := n.navigation.share.ParseShareLink(shareLink) //解析参数
	if err != nil {
		return "", gerror.Wrap(err, "GetShareFolder1")
	}
	shareId, err := strconv.Atoi(link.Get("id"))
	if err != nil {
		return "", gerror.Wrap(err, "GetShareFolder2")
	}
	shareData, err := n.navigation.share.GetShareData(shareId)
	if err != nil {
		return "", gerror.Wrap(err, "GetShareFolder3")
	}

	lookUser, err := user.Lookup(shareData.User)
	if err != nil {
		return "", err
	}

	group, err := user.LookupGroupId(lookUser.Gid)
	if err != nil {
		return "", err
	}

	timeNow := time.Now().Format("20060102")
	shareFolder := fmt.Sprintf("/home/%s/Photos/publicShare/%s", shareData.User, timeNow)

	if !utils.Exists(shareFolder) {

		os.MkdirAll(shareFolder, 0755)
		utils.ShellWithString(fmt.Sprintf("chown %s:%s %s -R", shareData.User, group.Name, shareFolder))
	}

	return shareFolder, nil
}

func (n *FileNav) HandleUploadShareFile(c *gin.Context, fn func(file HttpFile, dir string, totalIndex, index uint64, autoDelete bool, size int64, uname string, lastModTime int64) (string, error)) (interface{}, error) {

	file, err := c.FormFile("file")
	if err != nil {
		return "", err
	}
	shareLink := c.PostForm("shareLink")
	if shareLink == "" {
		return "", errors.New("shareLink empty")
	}
	//总分片数
	totalIndex, err := strconv.ParseUint(c.PostForm("total_index"), 10, 64)
	if err != nil {
		return "", err
	}
	if totalIndex == 0 {
		return "", errors.New("total_index as 0")
	}
	//当前索引
	index, err := strconv.ParseUint(c.PostForm("index"), 10, 64)
	if err != nil {
		return "", err
	}
	size, e := strconv.ParseInt(c.PostForm("size"), 10, 64)
	if e != nil {
		return false, e
	}
	if size <= 0 {
		return false, errors.New("size as 0")
	}
	path := c.PostForm("path")
	if path == "" {
		return "", errors.New("path empty")
	}
	uname, err := n.UploadShareFileVerify(shareLink, c.PostForm("pwd"))
	if err != nil {
		return "", err
	}
	//最后文件修改时间不处理错误容错零值
	lastModTime, _ := strconv.ParseInt(c.PostForm("last_mod_time"), 10, 64)

	uploadShareFilePath, err := fn(&multipartHttpFile{h: file}, path, totalIndex, index, c.PostForm("auto_delete") == "1", size, uname, lastModTime)
	if err != nil {
		return "", err
	}

	// 文件全部上传完成
	if index == totalIndex {
		sharePath := sqlitemodel.SharePath{
			Path:       uploadShareFilePath,
			CreateData: time.Now(),
		}

		db := n.dbGetter.DbGet()
		err = db.Create(&sharePath).Error
		if err != nil {
			return "", err
		}

		if sharePath.Id == 0 {
			db.Where("path = ?", uploadShareFilePath).Select("id").First(&sharePath)
		}

		var prevShareConfig sqlitemodel.ShareConfig
		err = db.Model(&sqlitemodel.ShareConfig{}).Where("share_link = ?", url.QueryEscape(shareLink)).Select("paths_id").First(&prevShareConfig).Error
		if err != nil {
			return "", err
		}

		pathIds := strings.Split(prevShareConfig.PathsId, ",")
		pathIds = append(pathIds, strconv.Itoa(sharePath.Id))

		err = db.Model(&sqlitemodel.ShareConfig{}).Where("share_link = ?", url.QueryEscape(shareLink)).Update("paths_id", strings.Join(pathIds, ",")).Error
	}

	return uploadShareFilePath, err
}

// DesktopList 桌面列表
func (n *FileNav) DesktopList(username string) []interface{} {
	short := n.ShortcutList(username)
	quickConnection := n.GetQuickConnection(username)
	return append(short, quickConnection...)
}

// GetQuickConnection 获取网页快捷方式
func (n *FileNav) GetQuickConnection(uName string) []interface{} {
	QuickConnectionData := make([]QuickConnectionInfo, 0)
	result := make([]interface{}, 0)
	bQuickConnection, _ := utils2.GetBUserConfig(uName, constant.QuickConnectionFile)
	if len(bQuickConnection) != 0 {
		_ = json.Unmarshal(bQuickConnection, &QuickConnectionData)
	}
	for _, val := range QuickConnectionData {
		info := NewQuickConnectionInfo{}
		info.Id = val.Id
		info.FRealType = "quicklink"
		info.FType = "quicklink"
		info.Name = val.Name
		info.Path = val.Path
		info.Size = 0
		info.SizeFriendly = "0B"
		info.CTime = val.CTime
		info.ATime = val.CTime

		result = append(result, info)
	}

	return result
}

// ShortcutList 快捷方式数据
func (n *FileNav) ShortcutList(uName string) []interface{} {
	result := make([]interface{}, 0)
	//shortcutDir, err := n.GetUserShortcutDir(uName)
	dir, err := n.GetDesktopDir(uName)
	if err != nil {
		return result
	}
	dirs, err := os.ReadDir(dir)
	if err != nil {
		return result
	}
	for _, v := range dirs {
		path := filepath.Join(dir, v.Name())
		res, err1 := os.ReadFile(path)
		if err1 != nil {
			continue
		}
		var oexeDate utils.OexeDate
		if err = json.Unmarshal(res, &oexeDate); err != nil {
			continue
		}

		folder, err2 := n.fOptGetter.GetFolder(path)
		if err2 != nil {
			continue
		}
		if folder.FRealType == "oexe" {
			base := strings.TrimSuffix(folder.Name, ".oexe")
			folder.Name = base
		}
		folder.Path = oexeDate.Link
		result = append(result, *folder)
	}
	return result
}
